/**
 * This file is part of Sonedyan.
 * 
 * Sonedyan is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any
 * later version.
 *
 * Sonedyan is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Octave; see the file COPYING.  If not
 * see <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2009-2012 Jimmy Dubuisson <jimmy.dubuisson@gmail.com>
 */

package org.unige.mpej.eckmann.sonedyan.enron.graph;

import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import javax.mail.Message.RecipientType;

import org.unige.mpej.eckmann.sonedyan.enron.bean.Actor;
import org.unige.mpej.eckmann.sonedyan.enron.bean.Email;
import org.unige.mpej.eckmann.sonedyan.enron.bean.EnronMessage;
import org.unige.mpej.eckmann.sonedyan.enron.bean.Link;
import org.unige.mpej.eckmann.sonedyan.enron.db.DatabaseHandler;

import org.apache.log4j.Logger;

import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;

/**
 * Enron graph generator
 */
public class GraphGenerator
{
	private static Logger log = Logger.getLogger(org.unige.mpej.eckmann.sonedyan.enron.graph.GraphGenerator.class);
	
	private DatabaseHandler conn;
	
	/**
	 * class constructor
	 */
	public GraphGenerator(DatabaseHandler conn)
	{
		this.conn = conn;
	}
	
	/**
	 * get the graph of 1 to 1 messages between Enron actors
	 */
	public Graph<Actor,Link> generateEnron1To1MessagesGraph()
	{
		try
		{
			DirectedSparseGraph<Actor,Link> graph = new DirectedSparseGraph<Actor,Link>();
			
			List<EnronMessage> list = this.conn.getUniqueRecipientMessages();
			
			if (list != null)
			{
				log.debug("Number of unique recipients messages: " + list.size());
				
				// set of actors indexed by their ID
				Hashtable<String, Actor> actors = new Hashtable<String, Actor>();
				
				for (EnronMessage message : list)
				{
					String fromAddress = EnronMessage.getAddressAsString(message.getFrom());
					String toAddress = EnronMessage.getAddressesAsString(message.getToRecipients());
					
					if (Email.isEnronEmail(fromAddress) && Email.isEnronEmail(toAddress))
					{
						Actor fromActor = this.conn.getActorByAddress(fromAddress);
						Actor toActor = this.conn.getActorByAddress(toAddress);
						
						if (fromActor != null && toActor != null && !fromActor.getId().equals(toActor.getId()))
						{
							// be careful to use the same actor instances
							String fromId = fromActor.getId();
							String toId = toActor.getId();
							
							if (actors.get(fromId) != null)
							{
								fromActor = actors.get(fromId);
							}
							else
							{
								actors.put(fromId, fromActor);
							}
							
							if (actors.get(toId) != null)
							{
								toActor = actors.get(toId);
							}
							else
							{
								actors.put(toId, toActor);
							}
							
							// add the 2 vertices (ignored if already present)
							graph.addVertex(fromActor);
							graph.addVertex(toActor);
							
							// create a link object
							Link link = new Link();
							link.setFromActorId(fromActor.getId());
							
							// remove '<' & '>' characters from messageId
							// <messageId> -> messageId
							link.setLinkId(message.getId().replace("<", "").replace(">", ""));
							
							link.setToActorId(toActor.getId());
							link.setStartDate(message.getSentDate());
							link.setType(RecipientType.TO);
							
							// add the edge to the graph
							graph.addEdge(link, fromActor, toActor);
						}
						else
						{
							log.info("Message not added to the graph: one of the actors is not part of the set of defined actors");
						}
					}
					else
					{
						log.info("Message not added to the graph: one of the addresses is not an Enron email");
					}
				}
			} // if (list != null)
			else
			{
				log.info("No result was returned");
			}
			
			return graph;
		}
		catch(Exception e)
		{
			log.error("An error occured: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get the graph of 1 to 1 messages colinks between Enron actors
	 */
	public Graph<Actor,Link> generateEnron1To1ColinksGraph()
	{
		List<EnronMessage> list = this.conn.getUniqueRecipientMessages();
		
		return generateEnron1To1ColinksGraph(list);
	}
	
	/**
	 * get the graph of 1 to 1 messages colinks between Enron actors between two specified dates
	 */
	public Graph<Actor,Link> generateEnron1To1ColinksGraphInDateRange(Date beginDate, Date endDate)
	{
		List<EnronMessage> list = this.conn.getUniqueRecipientMessagesInDateRange(beginDate, endDate);
		
		return generateEnron1To1ColinksGraph(list);
	}
	
	/**
	 * get the graph of 1 to 1 messages colinks between Enron actors
	 */
	public Graph<Actor,Link> generateEnron1To1ColinksGraph(List<EnronMessage> list)
	{
		try
		{
			UndirectedSparseGraph<Actor,Link> graph = new UndirectedSparseGraph<Actor,Link>();
			
			if (list != null)
			{
				log.debug("Number of unique recipients messages: " + list.size());
				
				// keep track of the nodes children
				Hashtable<String, HashSet<String>> table = new Hashtable<String, HashSet<String>>();
				// set of actors indexed by their ID
				Hashtable<String, Actor> actors = new Hashtable<String, Actor>();
				
				for (EnronMessage message : list)
				{
					String fromAddress = EnronMessage.getAddressAsString(message.getFrom());
					String toAddress = EnronMessage.getAddressesAsString(message.getToRecipients());
					
					// if the addresses are Enron emails
					if (Email.isEnronEmail(fromAddress) && Email.isEnronEmail(toAddress))
					{
						Actor fromActor = this.conn.getActorByAddress(fromAddress);
						Actor toActor = this.conn.getActorByAddress(toAddress);
						
						// if the actors can be retrieved
						if (fromActor != null && toActor != null && !fromActor.getId().equals(toActor.getId()))
						{
							// be careful to use the same actor instances
							String fromId = fromActor.getId();
							String toId = toActor.getId();
							
							if (actors.get(fromId) != null)
							{
								fromActor = actors.get(fromId);
							}
							else
							{
								actors.put(fromId, fromActor);
							}
							
							if (actors.get(toId) != null)
							{
								toActor = actors.get(toId);
							}
							else
							{
								actors.put(toId, toActor);
							}
							
							// get the set of fromActor children if any
							HashSet<String> fromSet = table.get(fromId);
							
							log.debug("Adding " + toId + " to the list of " + fromId + " children");
							
							// check if toActor is already in the list of fromActor children
							if (fromSet != null && !fromSet.contains(toId))
							{
								// add toActor child
								fromSet.add(toId);
							}
							else
							{
								// create a new children set
								HashSet<String> children = new HashSet<String>();
								children.add(toId);
								table.put(fromId, children);
							}
							
							// get the set of toActor children if any
							HashSet<String> toSet = table.get(toId);
							
							// if we have a colink, let's add it to the graph
							if (toSet != null && toSet.contains(fromId))
							{
								log.debug("Adding a colink between " + fromId + " (known flag: " + fromActor.isKnown() + ") and " + toId + " (known flag: " + toActor.isKnown() + ")");
								
								// add the 2 vertices (ignored if already present)
								graph.addVertex(fromActor);
								graph.addVertex(toActor);
								
								// create a link object
								Link link = new Link();
								link.setFromActorId(fromActor.getId());
								
								// remove '<' & '>' characters from messageId
								// <messageId> -> messageId
								link.setLinkId(message.getId().replace("<", "").replace(">", ""));
								
								link.setToActorId(toActor.getId());
								link.setStartDate(message.getSentDate());
								link.setType(RecipientType.TO);
								
								// add the colink to the graph
								graph.addEdge(link, fromActor, toActor);
							}
						}
						else
						{
							log.info("Message not added to the graph: one of the actors is not part of the set of defined actors");
						}
					}
					else
					{
						log.info("Message not added to the graph: one of the addresses is not an Enron email");
					}
				}
			} // if (list != null)
			else
			{
				log.info("No result was returned");
			}
			
			return graph;
		}
		catch(Exception e)
		{
			log.error("An error occured: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get the graph of colinks between Enron actors
	 */
	public Graph<Actor,Link> generateEnronColinksGraph(List<Link> list)
	{
		try
		{
			UndirectedSparseGraph<Actor,Link> graph = new UndirectedSparseGraph<Actor,Link>();
			
			if (list != null)
			{
				log.debug("Number of links: " + list.size());
				
				// keep track of the nodes children
				Hashtable<String, HashSet<String>> table = new Hashtable<String, HashSet<String>>();
				// set of actors indexed by their ID
				Hashtable<String, Actor> actors = new Hashtable<String, Actor>();
				// keep track of the message id 
				
				for (Link link : list)
				{
					// be careful to use the same actor instances
					String fromId = link.getFromActorId();
					String toId = link.getToActorId();
					
					Actor fromActor = actors.get(fromId);
					Actor toActor = actors.get(toId);
					
					if (fromActor == null)
					{
						fromActor = this.conn.getActorById(fromId);
						actors.put(fromActor.getId(), fromActor);
					}
							
					if (toActor == null)
					{
						toActor = this.conn.getActorById(toId);
						actors.put(toActor.getId(), toActor);
					}
							
					// get the set of fromActor children if any
					HashSet<String> fromSet = table.get(fromId);
							
					log.debug("Adding " + toId + " to the list of " + fromId + " children");
							
					// check if toActor is already in the list of fromActor children
					if (fromSet != null && !fromSet.contains(toId))
					{
						// add toActor child
						fromSet.add(toId);
					}
					else
					{
						// create a new children set
						HashSet<String> children = new HashSet<String>();
						children.add(toId);
						table.put(fromId, children);
					}
							
					// get the set of toActor children if any
					HashSet<String> toSet = table.get(toId);
					
					// if we have a colink, let's add it to the graph
					if (toSet != null && toSet.contains(fromId))
					{
						log.debug("Adding a colink between " + fromId + " (known flag: " + fromActor.isKnown() + ") and " + toId + " (known flag: " + toActor.isKnown() + ")");
						
						// add the 2 vertices (ignored if already present)
						graph.addVertex(fromActor);
						graph.addVertex(toActor);
						
						// add the colink to the graph
						graph.addEdge(link, fromActor, toActor);
					}
				} // for
			} // if (list != null)
			else
			{
				log.info("No result was returned");
			}
			
			return graph;
		}
		catch(Exception e)
		{
			log.error("An error occured: " + e.getMessage());
			return null;
		}
	}
	
	/**
	 * get the graph of colinks between Enron actors between two specified dates
	 */
	public Graph<Actor,Link> generateEnronColinksGraphInDateRange(Date beginDate, Date endDate)
	{
		List<Link> list = this.conn.getLinksInDateRange(beginDate, endDate);
		
		return generateEnronColinksGraph(list);
	}
	
}
